Workstation Monitoring System

Developed by:
Aditya Arcot, Devin Singh, Yi Wang


Demonstration Video


Project Objective

The goal of our project was to design a Workstation Monitoring embedded system that determines if users are present at their workstations and registers relevant data regarding users. Such data includes their Names, IDs, number of accesses and amount of time spent at each workstation. The Workstation status should be remotely accessible to all users by means of a website.


Introduction

The Workstation Monitoring system features a GUI designed on Pygame and shown on the PiTFT screen that describes if a workstation is available, and if not, the approximate amount of time remaining for a particular workstation to become available. A user registers to use a workstation by scanning their associated RFID tag. The workstation will then become unavailable on the User Interface screen, and a PI cam will begin to periodically monitor if the user is still at the workstation. If the camera detects that a user is no longer present at a workstation, the workstation will become available. The workstation will also become available when the time remaining for workstation use becomes zero. If multiple users have registered for multiple workstations, the camera will periodically rotate via a camera servo motor to continuously monitor the multiple workstations that are in use. User data such as the number of individual workstation accesses and the amount of time a user spends at a workstation is logged to a database. The workstation availability and approximate time remaining is also logged to the database. This database is then accessed from a webpage hosted on an Apache web server located on the Raspberry Pi, allowing users to view workstation status and user information remotely. Administrators can also use the website to directly modify a user’s name.


Design

A flowchart describing the Workstation Monitoring system operation is shown below:

Generic placeholder image
Figure 1: Flow of Program

The following diagram shows the different components of the system:

Generic placeholder image
User Interface:

A user interface is designed to display the status of two stations on a PiTFT. When a user is logged in to a station, the PiTFT would display the user name and the remaining time that the user can use the workstation. The name of the user can be modified on the database through a web page. These working principles are explained in detail in the ‘Database and Website’ section. When no one is logged in to a station, the PiTFT would display this station as available. The first user that logged in would be assigned to station 1. If another RFID tag is detected, the user would be assigned to whichever station that is available.

Generic placeholder image
Figure 2: Program GUI
RFID Reader:

The PN532 breakout board can be communicated with via SPI, I2C and UART. We decided to use UART to communicate with the board, as it was the most well documented and contained the clearest instructions to configure. The transmit, receive, VCC and ground pins of the PI were necessary to establish serial communication with the RFID reader. The PN532 board must be set-up to accept UART communication by setting both the SEL0 and SEL1 pins located on the board to their OFF positions. The SEL0 and SEL1 pin locations are shown below:

Generic placeholder image
Figure 3: PN532 Breakout Board

Using the libnfc library, we were able to communicate with the PN532 breakout board through terminal commands on the Raspberry Pi. Using this method, we could simply send a command to the terminal that read from the breakout board for 36 seconds. After 36 seconds, if an RFID tag was not detected, the reading process would timeout. If a tag is present, the process would display the ID associated with the particular tag in the terminal window. Initially, we intended on using system commands from Python to scan for RFID tags and return what was scanned, however, the RFID scanning process through the terminal was blocking and proved difficult to accurately read responses from. Instead, we decided to install the Adafruit Circuit Python library for PN532 UART connection and directly access the RFID reader from our Python code. Doing this proved much easier and made the RFID reader better integrated within our code.

Person Detection via OpenCV:

Apart from using RFID to register the user that wishes to log in to a workstation, we also enhanced the security of the system by adding person detection. If a user is logged in to a particular workstation with an RFID card and is not present in the workstation for a predefined amount of time, the user would be evicted. Person detection is achieved by using the OpenCV library in our project. The code we used as a reference and is linked in our references sections of this page. The code from this webpage can detect humans along with animals and many other objects. Since only human recognition is needed in this project, the person detection process would set a signal ‘person_present.value’ to high only when a person is detected. The camera constantly monitors both workstations through the person detection process and determines if a person is present at each workstation.

Camera Gimbal:

To monitor both workstations with one camera, we used a servo motor as the camera gimbal. The RPi camera should be connected to the Raspberry Pi CSI port by a cable (as shown in the figure below). The RPi camera is attached to a servo motor, and the steering arm of the servo motor is fixed on a box in the front of both workstations. Hardware PWM is used for the servo motor control instead of software PWM to avoid servo jitter, and using hardware PWM can also save the process time. Each time before using the hardware PWM, we need to use ‘sudo pigpiod’ which can launch the ‘pigpio’ library as a daemon. On the Raspberry Pi, only GPIO Pins 12, 13, 18, and 19 can be used for hardware PWM, and GPIO 12 is used as the PWM input in this project. The power supply to the servo motor (the red wire) should be connected to the 5V output on the Raspberry Pi. And the rotation angle of the servo can be controlled by changing the duty cycle of the PWM signal.

Generic placeholder image
Figure 4: Pi Camera and Servo Connections

If both workstations are logged in, the servo would rotate and monitor both workstations. For example, the servo is default to monitor workstation 1 on the left first. The Raspberry Pi would run the person detection process to determine if a person is present at the workstation 1. If a person is present, then the servo would rotate and monitor workstation 2. If a person is not present, the servo would run the person detection process another few times before it evicts the user, automatically update the station as being available, and rotate to workstation 2. Once the servo rotates to workstation 2, the Raspberry Pi would again run the person detection process and repeat the aforementioned steps. If only one user is logged in to the workstations, the camera would rotate and only monitor the workstation that is being used.

Multiprocessing:

Workstation status information such as workstation availability and the remaining time a user has to use a particular workstation should be updated and remain accurate, regardless if camera detection is occurring or not. Recall that when detecting if users are present, the camera would first rotate to the appropriate workstation, and then utilize our OpenCV recognition algorithm to determine if a person is present in the camera frame or not. Taking the picture and analyzing via the neural network that was designed was a blocking process and took time. This meant that when checking if a user was present, the timer that was displayed on our PiTFT, would be paused. To fix this, we utilized the multiprocessing library to create two independent processes that operated in parallel. The action of rotating the camera and checking the captured camera image for people was in its own process, while updating the PiTFT screen and the database was kept in a second process. There were variables for each workstation that were sent from the camera check process to the program display process, indicating whether a user was detected or not. If a user was not detected, the availability of the workstation was set to available. Note that the camera checked featured mechanisms that allowed for multiple checks before concluding that a user was no longer present. As previously explained, this was done to ensure a user had actually left the workstation, and did not leave the workstation temporarily.

Database and Website:

The database was created using MariaDB, an open-source database management system. The database was hosted on the Raspberry Pi, and supported two way communication with our Python code. MariaDB was installed using apt-get, and a new ‘default’ user was set up having its own credentials, with root permissions to edit database contents. The database consisted of two tables that contained user information and workstation status respectively.

The first table contained user information in relation to the workstations, such as the number of times a user has accessed a particular workstation and the amount of time a user has spent on each. This is done to exemplify that information can be stored regarding user activity on each workstation. The data stored in this table can be incremented using functions that we made in Python, which utilized the mysql-connector library to communicate with the database.

The second table summarizes information displayed on the PiTFT screen, including the workstation status and the remaining time a user has on a workstation that is in use. Similar to the previous table, Python functions were defined that allowed us to modify the contents of the table in real-time.

The contents of the table were able to be viewed via a series of webpages we created that were hosted on an Apache 2 server hosted on the Raspberry Pi. The webpages were designed using PHP and mysql_connect, allowing us to communicate with the database that was being written to by the PI. There were four web pages designed for our Workstation monitoring system. The first webpage communicated with the second data table located on the database, which contained workstation occupancy status, and the remaining time a user has on a particular workstation. This would allow users to know Workstation status remotely, without attending the room where the workstations are located to determine if one could be used. The second webpage summarized the contents of the first table stored in our database, which contained user information regarding the number of accesses and amount of time spent on each workstation. Similar to the first webpage, the second webpage was updated based on the contents of the table it was associated with. This again allowed users to remotely access and view the contents stored in our database. Two more webpages were developed that allowed the administrator to rename users and to remove users if necessary. Renaming users is especially helpful when a new individual is registered within our workstation monitoring system. Recall that in this case, the workstation monitoring system will assign the new user a username. The administrator can then be requested to modify the user’s assigned name to whatever they wish.

Issues were encountered with communicating to the database via PHP. The commands that were sent to the database required special formatting that was not immediately clear. After experimentation, we were able to successfully determine the formatting required to write and read from the database via our web pages.

Altogether, the database was communicated with the Raspberry Pi and with the web pages we developed simultaneously. This aspect of the project also makes it easy to add more workstation monitoring systems to our network. This is because other workstation monitoring systems will have access to all the data stored in the database.


Testing

Our system was designed to monitor two workstations. For this reason, we first tested the monitoring capabilities of individual workstations, then we tested the monitoring capabilities when both workstations were occupied. When turning on the Raspberry Pi, a bash script was set to run name start_project.sh, which first reinitialized the pigpiod library for hardware PWM usage, and then started our shutdown script in the background, and finally started our main Python code. The workstations initially begin as available.


Generic placeholder image
Figure 6: All workstations are available

When an RFID tag was scanned, the first workstation will immediately be assigned the user's name retrieved from the database.


Generic placeholder image
Figure 7: Workstation one is now occupied

The camera gimbal will point the camera in the workstation 1 direction all times in this situation, as only workstation one is in use. When blocking the camera or moving out of its frame, the station would automatically update to being available after a few camera checks. We utilized multiple camera checks to ensure that a user is actually no longer present and that they did not temporarily leave the frame. Testing the second workstation alone also yielded similar results. In this case, if only workstation two is occupied, the camera would continuously point at workstation 2. By blocking the camera or leaving the camera frame, the station would also eventually become available.


We also checked if the database updated when a user scans into a workstation and when they leave the workstation. This was done using the webpage we designed.


Generic placeholder image
Figure 8: Database summmary webpage displaying current user information stored

When each workstation was accessed, we could see that the database would accurately increment the particular workstation access count for each user. Furthermore, when a station becomes available after a user has either left or their workstation time has reached 0, we could also see the overall workstation time for the user update with the amount of time they spent at the workstation.


When single or multiple workstations were in use, both the PiTFT screen and our website would update the workstation status to reflect that a workstation was no longer available.


Generic placeholder image
Figure 9: Both workstations are occupied

Generic placeholder image
Figure 10: Webpage displays the same workstation status as in Figure 9

If both workstations were occupied, the camera gimbal successfully turned periodically to monitor both workstations. It was accurately able to differentiate between when a user was missing from the first workstation and the second workstation, and consistently updated the availability status of workstations when a user was missing from either. It also successfully updated the database with the correct number of access for both users and the correct amount of time spent on a particular workstation when a user leaves their workstation or if their station time ran out.


Finally, when initiating a new user into the system, our workstation successfully assigned them a username based on the number of users present in the system.


Generic placeholder image
Figure 11: New user present at workstation two

Usernames were also able to be edited using the Edit Username webpage, and users could also be removed from our database using the remove user webpage.


Generic placeholder image
Figure 12: User 3 username being modified to Yi

Generic placeholder image
Figure 13: User 3 is now named Yi in database

Results

Our team successfully implemented and exceeded the requirements for our Workstation Monitoring system that were decided during project week 1. Originally, we intended to build a Workstation monitoring system that simply monitored if users were present via a stable camera and modified workstation availability status based on when an RFID tag was scanned and if a user was in the camera frame. We further expanded our project by introducing a moving camera that could periodically monitor multiple workstations at a time, and by introducing a central database that could be communicated with through our Python code and through a website of our own design, that stored relevant user and workstation information. The Workstation Monitoring system successfully monitored two workstations at a time, and accurately determined if a user was present for both stations using OpenCV person recognition and the camera gimbal. Furthermore, the system successfully communicated with our central database and logged relevant user and workstation information. Finally, our website allowed users to view relevant workstation and user information remotely, and allowed administrators to remove users and also modify their usernames.


Conclusion

When developing our Workstation monitoring system, the main issue we had was with parallelizing camera checking, camera rotation and updating the PiTFT screen and database at the same time. Initially, our camera would rotate on set periods defined by the main python process that updated the PiTFT screen and database information. The camera checking was simply done in parallel with this rotation and updating of the screen and database. This caused issues with synchronizing between camera checks and updating workstation availability. For example, Workstation 1 would be detected as having no user present, however, the camera would rotate and then Workstation 2 would be marked as available instead of Workstation 1. To fix this problem, we synchronized camera rotation with taking pictures and feeding them to our OpenCV code. By doing this, we avoided updating the incorrect workstation, and ensured that workstation checking and camera rotation were completely synchronized. This was the largest issue faced, and allowing for camera checks to happen completely independently of camera rotations would definitely not have allowed our system to meet the requirements we established for our system.


By solving this issue, we could definitely conclude that our Workstation Monitoring system accurately determined when a workstation was in use by a user and updated our database that could be accessed remotely, allowing others to view whether workstations were available remotely. Our workstation monitoring system also updated our database with information regarding individual user usage for each workstation, and also allowed administrators to remove users if necessary, and modify user names.



Work Distribution

Aditya was responsible for the PyGame display and getting the individual components such as the RFID reader, camera Gimbal and image identification to work together and meet the requirements of our embedded system. Devin was responsible for creating the web pages that communicated with the database, and writing initial Python code that communicated with the database from the PI, making integration of the database with our main code smoother.Yi was responsible for setting up all hardware, including the RFID reader, camera module and the camera gimbal servo motor so that it could be easily integrable with the main python program.




Generic placeholder image

Aditya Arcot

as3745@cornell.edu

Designed PyGame Display, worked on synchronizing camera rotation with image detection, and Python to database communication.

Generic placeholder image

Devin Singh

ds2392@cornell.edu

Designed webpages and worked on database/web server set-up, and communication between webpages and python.



Generic placeholder image

Yi Wang

yw2586@cornell.edu

Set up hardware including RFID, camera identifcation and servo motor control signals.


Parts List - Provided by lab

Total: $147.00


References

Database and Web Page References and Tutorials:

MariaDB Set-up Tutorial
Apache Web Server Set-up
PHP to access Database
Convert Database Information into Table

OpenCV Person Recognition, Training Data Download and setup instructions:

Object and Animal Recognition

RFID Setup:

Previous RFID Project
RFID UART Set-up Guide

Hardware PWM library Setup:

Hardware PWM

Code Appendix


// god_version.py (The main program)

# Workstation Monitoring System for the final project of ECE 5725
  # Written by Devin Singh (ds2392), Aditya Arcot (as3745), Yi Wang (yw2586)

  import RPi.GPIO as GPIO
  import serial
  import time
  import os
  import pygame
  from pygame.locals import *
  from time import sleep
  import pigpio # for using hardware PWM
  import cv2 # for object detection
  from adafruit_pn532.uart import PN532_UART # for RFID 
  import os
  import mysql.connector as database
  import multiprocessing
  
  username = os.environ.get("username")
  password = os.environ.get("password")
  
  #Connect to mariadb
  connection = database.connect(user='default', password='password',
   host="localhost", database="final_project")
  
  #Cursor to move through rows of 'users' table
  cursor = connection.cursor()
  
  os.putenv('SDL_VIDEODRIVER','fbcon') #Display on PiTFT
  os.putenv('SDL_FBDEV','/dev/fb0')
  os.putenv('SDL_MOUSEDRV','TSLIB') #Track mouse clicks on PiTFT
  os.putenv('SDL_MOUSEDEV','/dev/input/touchscreen')
  
  pygame.init()
  
  
  pygame.mouse.set_visible(False)
  # character font displayed on piTFT
  WHITE = 255, 255, 255
  BLACK = 0,0,0
  GREEN = 0, 255, 0
  RED = 255, 0, 0
  screen=pygame.display.set_mode((320, 240))
  
  
  #uart setup
  uart = serial.Serial("/dev/ttyS0", baudrate=115200, timeout=1)
  pn532 = PN532_UART(uart, debug=False)
  ic, ver, rev, support = pn532.firmware_version
  #Configure PN532
  pn532.SAM_configuration()
  
  #Wrokstation user   workstation : user_id
  workstation_user = { 1:1, 2:1}
  user_id = {0: "Available", 1: "Devin", 2:"Yi", 3: "Adi"}
  rfid_tags = { 1:"Available", 0:"-"}
  my_font = pygame.font.Font(None, 25)
  my_buttons = {(160, 40):'Workstation Activity', (60, 100):'Station 1:', (60, 140):'Station 2'}
  workstation_status = {(160, 100): '%s'%(rfid_tags[workstation_user[1]]), (160, 140): '%s'%(rfid_tags[workstation_user[2]])} #store the RFID tags of each user
  workstation_time = {1:0, 2:0} 
  my_times = { (240, 100):'%d'%(workstation_time[1]), (240, 140):'%d'%(workstation_time[2])} # remaining login time for each user
  
  screen.fill(BLACK)
  
  my_buttons_rect= {}
  
  for text_pos, my_text in my_buttons.items():
      text_surface = my_font.render(my_text, True, WHITE)
      rect = text_surface.get_rect(center = text_pos)
      screen.blit(text_surface, rect)
      my_buttons_rect[my_text] = rect
  
  
  for text_pos, my_text in workstation_status.items():
      if (my_text == "Available"):
          text_surface = my_font.render(my_text, True, GREEN)
      else:
          text_surface = my_font.render(my_text, True, RED)
      rect = text_surface.get_rect(center = text_pos)
      screen.blit(text_surface, rect)
  
  pygame.display.flip()
  
  #define switches to be used
  SW1 = 17
  SW2 = 22
  SW3 = 23
  SW4 = 27
  LED = 26
  
  
  GPIO.setmode(GPIO.BCM)
  GPIO.setup(SW1, GPIO.IN, pull_up_down = GPIO.PUD_UP)
  GPIO.setup(SW2, GPIO.IN, pull_up_down = GPIO.PUD_UP)
  GPIO.setup(SW3, GPIO.IN, pull_up_down = GPIO.PUD_UP)
  GPIO.setup(SW4, GPIO.IN, pull_up_down = GPIO.PUD_UP)
  GPIO.setup(26,GPIO.OUT)
  
  # this function updates the workstation occupancy and remaining time to the database
  def update_ws_table(ws, status_, remaining_time):
    try:
      statement = "UPDATE ws_occupancy SET status_ = %s, remaining_time = %s WHERE workstation = %s"
  
      data = (status_, remaining_time, ws)
      cursor.execute(statement, data)
      connection.commit()
  
    except database.Error as e:
      print("Error entering database information from update_ws_table: {e}")
  
  
  def increment_access_count(first_name, WS):
    try:
      if (WS == 1):
        statement = "UPDATE users SET WS1_noof_access = WS1_noof_access  + 1 WHERE first_name = %s"
      elif(WS == 2):
        statement = "UPDATE users SET WS2_noof_access = WS2_noof_access  + 1 WHERE first_name = %s"
      data = (first_name,)
      cursor.execute(statement, data)
      connection.commit()
      print("Successfully incremented Workstation %s access count" % WS)
    except database.Error as e:
      print("Error entering database information from increment access count: {e}")
  
  
  def check_name(rfid):
      try:
          query = "SELECT COUNT(*) FROM users WHERE rfid = %s"
          cursor.execute(query, (rfid,))
          result = cursor.fetchone()
          if result[0] == 0:
             return False
          else:
              return True
      except Exception as e:
          print(f"Error: {e}")
  
  
  def check_num():
      try:
          statement = "SELECT COUNT(*) FROM users"
          cursor.execute(statement)
          result = cursor.fetchone()
          return result
      except Exception as e:
          print(f"Error: {e}")
  
  # update the user and the corresponding RFID to the database
  def add(first_name,rfid):
      try:  
          statement = "INSERT INTO users (first_name,rfid) VALUES (%s, %s)"
          data = (first_name, rfid)
          cursor.execute(statement, data)
          connection.commit()
          print("Successfully added user to database")
      except database.Error as e:
          print("Error entering database information: %s" % e)
  
  # return the user name to the corresponding RFID tag and update to the database
  def get_data(rfid):
      try:
        statement = "SELECT first_name, rfid FROM users WHERE rfid=%s"
        data = (rfid,)
        cursor.execute(statement, data)
        for (first_name, rfid) in cursor:
          print(f"Successfully retrieved {first_name}, {rfid}")
          return first_name
      except database.Error as e:
        print(f"Error retrieving entry from database: {e}")
        return False
  
  # update the userer name, the station that he is using and the remainging time to the database
  def modify_session_time(first_name, WS, session_time):
    try:
      if (WS == 1):
        statement = "UPDATE users SET WS1_total_time = WS1_total_time + %s WHERE first_name = %s"
      elif(WS == 2):
        statement = "UPDATE users SET WS2_total_time =  WS2_total_time + %s WHERE first_name = %s"
      data = (session_time, first_name)
      cursor.execute(statement, data)
      connection.commit()
      print("Successfully updated WS %d time" % WS)
    except database.Error as e:
      print("Error entering database information: {e}")
  
  # quit this python program if called 
  def quit_callback(channel):
      global code_run
      print("*************************Code exiting************")
      code_run.value= 0
     
  time_limit = 300 
  start_time = time.time()
  code_run = True
  
  #Open CV Functions
  #This is to pull the information about what each object is called
  classNames = []
  classFile = "/home/pi/Desktop/Object_Detection_Files/coco.names"
  with open(classFile,"rt") as f:
      classNames = f.read().rstrip("\n").split("\n")
  
  #This is to pull the information about what each object should look like
  configPath = "/home/pi/Desktop/Object_Detection_Files/ssd_mobilenet_v3_large_coco_2020_01_14.pbtxt"
  weightsPath = "/home/pi/Desktop/Object_Detection_Files/frozen_inference_graph.pb"
  
  #This is some set up values to get good results
  net = cv2.dnn_DetectionModel(weightsPath,configPath)
  net.setInputSize(320,320)
  net.setInputScale(1.0/ 127.5)
  net.setInputMean((127.5, 127.5, 127.5))
  net.setInputSwapRB(True)
  
  #This is to set up what the drawn box size/colour is and the font/size/colour of the name tag and confidence label   
  def getObjects(img, thres, nms, draw=True, objects=[]):
      classIds, confs, bbox = net.detect(img,confThreshold=thres,nmsThreshold=nms)
  #Below has been commented out, if you want to print each sighting of an object to the console you can uncomment below     
      #print(classIds,bbox)
      if len(objects) == 0: objects = classNames
      objectInfo =[]
      if len(classIds) != 0:
          for classId, confidence,box in zip(classIds.flatten(),confs.flatten(),bbox):
              className = classNames[classId - 1]
              if className in objects: 
                  objectInfo.append([box,className])
                  if (draw):
                      cv2.rectangle(img,box,color=(0,255,0),thickness=2)
                      cv2.putText(img,classNames[classId-1].upper(),(box[0]+10,box[1]+30),
                      cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),2)
                      cv2.putText(img,str(round(confidence*100,2)),(box[0]+200,box[1]+30),
                      cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),2)
                      pass
      
      return img,objectInfo
  
  # rotate the servo motor (the camera is attached to) 
  def rotate_cam(pos):
      if(pos == 0): # if the servo is pointing towards workstation 1 (on the left)
          print("camera turn right")
          pi_hw.hardware_PWM(12, 50, 90000)
          return 1
      elif(pos == 1):  # if the servo is pointing towards workstation 2 (on the right)
          print("camera turn left")
          pi_hw.hardware_PWM(12, 50, 45000)
          return 0
  
  #Camera Call function
  def check_camera(person_present1, person_present2, check, code_run):
      GPIO.add_event_detect(SW1, GPIO.FALLING, callback=quit_callback, bouncetime=300)
      pi_hw.hardware_PWM(12, 50, 45000)
      timeout1=0
      timeout2=0
      pos = 0 #position of camera
      print("process started")
      print(code_run.value)
      while(code_run.value==1):
          if(check.value == 0): # only work station 1 is being logged in or neither workstations are being used
              if(pos == 1):
                  pos = rotate_cam(pos)
                  time.sleep(5)
              print("Checking camera 1")
              os.system('raspistill -w 640 -h 480 -t 2000 -n -o  /home/pi/Desktop/image.jpg')
              img = cv2.imread("/home/pi/Desktop/image.jpg")
              result, objectInfo = getObjects(img,0.60,1, objects = ['person'])
              print("Result is")
              print(objectInfo)
              if (not objectInfo): # if a person if not detected in a camera fram
                  if(timeout1==1): 
                      person_present1.value = 0 # a person is not present at the workstation 1
                      timeout1 = 0
                      time.sleep(3)
                  else:   # wait for 3 seconds to take another picture
                      person_present1.value = 1 # a person is not present at the workstation 1
                      timeout1 = 1
                      time.sleep(3)
                      print("time")
              else:
                  person_present1.value = 1 # a person is not present at the workstation 1
                  timeout1 = 0 
  
          if(check.value == 1): # only the workstation 2 is being logged in
              if(pos == 0):
                  pos = rotate_cam(pos)
                  time.sleep(5)
              print("Checking camera 2")
              os.system('raspistill -w 640 -h 480 -t 2000 -n -o  /home/pi/Desktop/image.jpg')
              img = cv2.imread("/home/pi/Desktop/image.jpg")
              result, objectInfo = getObjects(img,0.60,1, objects = ['person'])
              print("Result is")
              print(objectInfo)
              if (not objectInfo):  # if a person if not detected in a camera fram
                  if(timeout2==1): 
                      person_present2.value = 0 # a person is not present at the workstation 2
                      timeout2 = 0
                      time.sleep(3)
                  else:  # wait for 3 seconds to take another picture
                      person_present2.value = 1 # a person is present at the workstation 2
                      timeout2 = 1
                      time.sleep(3)
              else:
                  person_present2.value = 1 # a person is present at the workstation 2
                  timeout2 = 0
                  
              
          if(check.value == 2): # both workstations are being used
              if(pos == 1):
                  pos = rotate_cam(pos) # rotate the servo (and camera) to workstation 1 if not already looking at workstation 1
                  time.sleep(5)
              print("Checking camera 1")
              os.system('raspistill -w 640 -h 480 -t 2000 -n -o  /home/pi/Desktop/image.jpg')
              img = cv2.imread("/home/pi/Desktop/image.jpg")
              result, objectInfo = getObjects(img,0.60,1, objects = ['person'])
              print("Result is")
              print(objectInfo)
              if (not objectInfo):  # if a person if not detected in a camera fram
                  if(timeout1==1):
                      person_present1.value = 0 # a person is not present at the workstation 1
                      timeout1 = 0
                      time.sleep(3)
                  else:   # wait for 3 seconds to take another picture
                      person_present1.value = 1 # a person is present at the workstation 1
                      timeout1 = 1
                      time.sleep(3)
              else:
                  person_present1.value = 1 # a person is present at the workstation 1
                  timeout1 = 0
              
              if(pos == 0):
                  pos = rotate_cam(pos) # rotate the servo (and camera) to workstation 2 if not already looking at workstation 1
                  time.sleep(5)
              print("Checking camera 2")
              os.system('raspistill -w 640 -h 480 -t 2000 -n -o  /home/pi/Desktop/image.jpg')
              img = cv2.imread("/home/pi/Desktop/image.jpg")
              result, objectInfo = getObjects(img,0.60,1, objects = ['person'])
              print("Result is")
              print(objectInfo)
              if (not objectInfo):  # if a person if not detected in a camera fram
                  if(timeout2==1): 
                      person_present2.value = 0 # a person is not present at the workstation 2
                      timeout2 = 0
                      time.sleep(5)
                  else:  # wait for 5 seconds to take another picture
                      person_present2.value = 1 # a person is present at the workstation 2
                      timeout2 = 1
                      time.sleep(5)
              else:
                  person_present2.value = 1 # a person is present at the workstation 2
                  timeout2 = 0
          cv2.waitKey(1)
  
  # this function returns the RFID scanned data     
  def rfid_read():
      uid = pn532.read_passive_target(timeout=0.5)
      if uid is None:
          return None
      else:
          la = [str(i) for i in uid]
          tag = ','.join(la)
          return tag
          
  pi_hw = pigpio.pi()
  #Declare Camera thread
  
  #Main while
  username1 = None
  def main_program(person_present1, person_present2,  check,code_run):
      GPIO.add_event_detect(SW1, GPIO.FALLING, callback=quit_callback, bouncetime=300)
      st1 = True
      st2 = True
      username1 = None
      username2 = None
      time1 = 0
      time2 = 0 
      pi_hw.hardware_PWM(12, 50, 45000) # initialize the servo towards workstation 1
      
      while(code_run.value == 1):
  
          if(workstation_user[1]==1 or workstation_user[2]==1): 
              temp = rfid_read() # get the RFID tag data
              if(temp != None):
                  tag = temp
                  print(tag)
                  if(workstation_user[1]==1): # workstation 1 is logged in
                      print(check_name(tag))
                      if(check_name(tag)): 
                          print(get_data(tag))
                          username1temp = get_data(tag)
                      else:
                          name = "User" + str(check_num()[0]+1)
                          add(name,tag) # update the user and the corresponding RFID to the database
                          username1temp = name
                      print(username2)
                      if(username2 != username1temp): # register the user to station 1 only if the user is not already logged in to station 2
                          username1 = username1temp
                          workstation_user[1] = 0
                          workstation_time[1] = 120 # reinitialize the remaining time for station 1 as 120 seconds
                  elif(workstation_user[2]==1):
                      if(check_name(tag)):
                          username2temp = get_data(tag)
                      else:
                          name = "User" + str(check_num()[0]+1)
                          add(name,tag) # update the user and the corresponding RFID to the database
                          username2temp = name
                      if(username1 != username2temp): # register the user to station 2 only if the user is not already logged in to station 1
                          username2 = username2temp
                          workstation_user[2] = 0
                          workstation_time[2] = 120 # reinitialize the remaining time for station 1 as 120 seconds
  
          # neither of the stations are being used
          if(workstation_user[1] and workstation_user[2]):
              workstation_status = {(160, 100): '%s' % (rfid_tags[1]), (160, 140): '%s' % (rfid_tags[1])}
              update_ws_table(1, "Available", workstation_time[1]) #updates the workstation 1 occupancy and remaining time to the database
              update_ws_table(2, "Available", workstation_time[2]) #updates the workstation 2 occupancy and remaining time to the database
              check.value = 0
  
          # only station 2 is being used
          elif(workstation_user[1] and not (workstation_user[2])):
              workstation_status = {(160, 100): '%s'%(rfid_tags[1]), (160, 140): '%s'%(username2)}
              update_ws_table(1, "Available", workstation_time[1])
              update_ws_table(2, username2, workstation_time[2])
              check.value = 1
  
          # only station 1 is being used
          elif(not workstation_user[1] and (workstation_user[2])):
              workstation_status = {(160, 100): '%s'%(username1), (160, 140): '%s'%(rfid_tags[1])}
              update_ws_table(1, username1, workstation_time[1])
              update_ws_table(2, "Available", workstation_time[2])
              check.value = 0
  
          # both stations are being used
          else:
              workstation_status = {(160, 100): '%s'%(username1), (160, 140): '%s'%(username2)}
              update_ws_table(1, username1, workstation_time[1])
              update_ws_table(2, username2, workstation_time[2])
              check.value = 2
                            
          my_times = { (240, 100):'%d'%(workstation_time[1]), (240, 140):'%d'%(workstation_time[2])}
          
          
          if(workstation_time[1]>0 ): # update the remainging time for workstation1
              if(time.time()-time1>1):
                  workstation_time[1] = workstation_time[1] - 1 # decrease the remaining time by 1 second
                  time1 = time.time()
                  if (workstation_time[1] == 116):
                      increment_access_count(username1, 1)
                  
                  if(workstation_time[1]<100): # evict the user if a person is not present in the workstation after 20 seconds
                      if(person_present1.value == 0):
                          workstation_user[1] = 1
                          
                          modify_session_time(username1,1,120-workstation_time[1]) # update the userer name, the station that he is using and the remainging time to the database
                          username1 = None
                          workstation_time[1] = 0
                  if(workstation_time[1]==1 and st1):
                      modify_session_time(username1,1,120)
                      st1 = False
                          
                  if(workstation_time[1]==0):
                      workstation_user[1] = 1
                      username1 = None
                      st1 = True
                      
          if(workstation_time[2]>0 ): # update the remainging time for workstation2
              if(time.time()-time2>1):
                  workstation_time[2] = workstation_time[2] - 1 # decrease the remaining time by 1 second
                  time2 = time.time()
                  if (workstation_time[2] == 116):
                      increment_access_count(username2, 2)
                  
                  if(workstation_time[2]<100): # evict the user if a person is not present in the workstation after 20 seconds
                      if(person_present2.value == 0):
                          workstation_user[2] = 1
                          modify_session_time(username2,2,120-workstation_time[2]) # update the userer name, the station that he is using and the remainging time to the database
                          username2 = None
                          workstation_time[2] = 0
                  
                  if(workstation_time[2]==1 and st2):
                      modify_session_time(username2,2,120)
                      st2 = False
                          
                  if(workstation_time[2]==0):
                      workstation_user[2] = 1
                      st2 = True
                      username2 = None
  
          # update the display on the piTFT
          for text_pos, my_text in my_buttons.items():
              text_surface = my_font.render(my_text, True, WHITE)
              rect = text_surface.get_rect(center = text_pos)
              screen.blit(text_surface, rect)
              my_buttons_rect[my_text] = rect
  
  
          for text_pos, my_text in workstation_status.items():
              if (my_text == "Available"):
                  text_surface = my_font.render(my_text, True, GREEN)
              else:
                  text_surface = my_font.render(my_text, True, RED)
              rect = text_surface.get_rect(center = text_pos)
              screen.blit(text_surface, rect)
              #my_buttons_rect[my_text] = rect
              
          for text_pos, my_text in my_times.items():
              if(my_text != "0"):
                  text_surface = my_font.render(my_text, True, RED)
                  rect = text_surface.get_rect(center = text_pos)
                  screen.blit(text_surface, rect)
                  my_buttons_rect[my_text] = rect
  
  
          pygame.display.flip()
          screen.fill(BLACK)
          time.sleep(1/60)
   
  if __name__ == "__main__":
  
      code_run = multiprocessing.Value('i', 1)
      person_present1 = multiprocessing.Value('i', 1)
      person_present2 = multiprocessing.Value('i', 1)
      check = multiprocessing.Value('i', 0)
      process = multiprocessing.Process(target = check_camera, args=(person_present1, person_present2, check, code_run,  ))
      process1 = multiprocessing.Process(target = main_program, args=(person_present1, person_present2, check, code_run,  ))
  
      process.start()
      process1.start()
  
      process.join()
      process1.join()
  
  
     
  

    
//shutdown.py (Background program to shutdown pi on button press)

import RPi.GPIO as GPIO
  import time
  import os
        
  # Set the GPIO mode and pin
  GPIO.setmode(GPIO.BCM)
  shutdown_pin = 23  # Change this to the GPIO pin you have connected the button to
        
  # Set up the button as an input with pull-up resistor
  GPIO.setup(shutdown_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        
  def shutdown(channel):
    print("Button pressed! Shutting down...")
    time.sleep(1)  # Optional delay to avoid accidental triggers
    os.system("sudo killall pigpiod") #To endsure proper working of hardware pwm
    os.system("sudo shutdown -h now") #shutdown the pi
  
  # Add event detection for the button press
  GPIO.add_event_detect(shutdown_pin, GPIO.FALLING, callback=shutdown, bouncetime=2000)
  
  try:
      # Keep the script running
      while True:
          time.sleep(1)
  except KeyboardInterrupt:
        # Clean up GPIO on keyboard interrupt
        GPIO.cleanup()
  

//startup_proj.sh(Shell script to run at startup)

sudo killall pigpiod
  sudo pigpiod
  sudo python /home/pi/project/shutdown.py &
  sudo python /home/pi/project/god_version.py
  

//index.php (Website home page)

<!DOCTYPE html>
  <html lang="en">


  <head>
      <meta chaset="UTF-8">
      <meta http-equivx="X-UA-Compatible" content="IE=edge">
      <meta http-equiv="refresh" content="5">
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>Workstation Monitoring System</title>
      <h1> Workstation Monitoring System </h1>


      <style>
          .button {
          background-color:  #00008b;
          border: none;
          color: white;
          padding: 15px 32px;
          text-align: center;
          text-decoration: none;
          display: inline-block;
          font-size: 16px;
          margin: 10px 10px;
          cursor: pointer;
          }
          .button:hover {
                  background-color: #77C3EC;
                  color: white;
                  }
</style>

  </head>


  <body>

  <!-- <button>Default Button</button> -->
<a href="database_summary.php" class="button">User Analytics</a>

</a>



<a href="edit_username.php" class="button">
      Edit Username
</a>

<a href="remove_user.php" class="button">
      Remove User
</a>

      <br>
      <hr>
      <br>            

      <h3> Workstation Occupancy List </h3>
      <style type="text/css">
          
          table {
          border-collaspe: collapse;
          width: 50%;
          height: 10%;
          color: #00008b;
          font-family: monospace;
          font-size: 25px;
          text-align: center; 
          }

          th{
              background-color:  #00008b;
              color: white;


          }
          th {
            text-align: center;
          }
          tr:nth-child(even) {background-color: #f2f2f2};
      </style>

      <table>
          <tr>
              <th>Workstation</th>
              <th>Status</th>
              <th>Time Remaining</th>
      <?php 
      
      $servername = "localhost"; 
      $username = "default"; 
      $password = "password"; 
      $databasename = "final_project"; 
      
      // CREATE CONNECTION 
      $conn = mysqli_connect($servername,  
          $username, $password, $databasename); 
      
      // GET CONNECTION ERRORS 
      if (!$conn) { 
          die("Connection failed: " . mysqli_connect_error()); 
      } 
      
      // SQL QUERY 
      $query = "SELECT workstation, status_,
         remaining_time FROM `ws_occupancy`;"; 
      
      try 
      { 
          $conn = new PDO( 
              "mysql:host=$servername;dbname=$databasename",  
              $username, $password); 
      
          $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
          $stmt = $conn->prepare($query); 
          // EXECUTING THE QUERY 
          $stmt->execute(); 
      
          $r = $stmt->setFetchMode(PDO::FETCH_ASSOC); 
          // FETCHING DATA FROM DATABASE 
          $result = $stmt->fetchAll(); 
          // OUTPUT DATA OF EACH Row
          

          $WS1_flag = 0;
          $WS2_flag = 0;
          foreach ($result as $row)  
          { 

          

              if ($row["workstation"]  == 1) { //If workstation 1 is being used
                  
                  if ($row["remaining_time"] == 0) {
                      echo "<tr><td>". "Workstation 1"  .  "</td><td> <strong> <font color = #006400>"  . $row["status_"] . "</strong></td><td>" . $row["remaining_time"]  .  "</td><tr>";

                  }

                  else {

                  echo "<tr><td>". "Workstation 1"  .  "</td><td>"  . $row["status_"] . "</td><td>" . $row["remaining_time"]  .  "</td><tr>";
                  $WS1_flag = 1;
                  }
                  
              }

              if ($row["workstation"]  == 2 ) { //If workstation 1 is being used


                  if ($row["remaining_time"] == 0) {
                      echo "<tr><td>". "Workstation 2"  .  "</td><td> <strong> <font color = #006400>"  . $row["status_"] . "</strong></td><td>" . $row["remaining_time"]  .  "</td><tr>";

                  }

                  else {
                  echo "<tr><td>". "Workstation 2"  .  "</td><td>"  . $row["status_"] . "</td><td>" . $row["remaining_time"]  .  "</td><tr>";
                  $WS1_flag = 1;
                  }
                  
              }

          
          }
          echo "</table>";
      } catch(PDOException $e) { 
          echo "Error: " . $e->getMessage(); 
      } 

      
      $conn->close(); 

      echo "<a href=\"database_summary.php\"> <button>Database</button> </a>"
      
      ?>

  </body>
</html>

      

//edit_username.php (edit username page)

<!DOCTYPE html>
  <html lang="en">


  <head>
      <meta chaset="UTF-8">
      <meta http-equivx="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>Modify Username</title>



      <style>
          .button {
          background-color:  #00008b;
          border: none;
          color: white;
          padding: 15px 40px;
          text-align: center;
          text-decoration: none;
          display: inline-block;
          font-size: 18px;
          margin: 10px 10px;
          cursor: pointer;
          }
          .button:hover {
                  background-color: #77C3EC;
                  color: white;
                  }
</style>
  </head>
  <h3>Modify Username</h3>
  <hr>
  <br>

  <body>

      
  
  <form action="edit_username.php" method="post">
      Current name: <input type="text", name="current_name">
      <br>
      <br>
      <br>

      New Name:<input type="text", name="new_name">
      <br>
      <br>
      <br>

      Password:<input type="password", name="password">
      <br>
      <br>
      <br>
      <input type="submit" class=button font size = -10>

<a href="index.php" class="button"> Home </a> 
</form>
<br> 
<?php 
      $run_flag = 0;
      $current_name = $_POST["current_name"];
      $new_name = $_POST["new_name"];
      $password = $_POST["password"];
      $servername = "localhost"; 
      $username = "default"; 
      $password = "password"; 
      $databasename = "final_project"; 
      
      // CREATE CONNECTION 
      $conn1 = mysqli_connect($servername,  
          $username, $password, $databasename); 
      
      // GET CONNECTION ERRORS 
      if (!$conn1) { 
          die("Connection failed: " . mysqli_connect_error()); 
      } 
   
      // SQL QUERY 
      $query = "SELECT `rfid` FROM `users` WHERE `first_name` ='" .  $current_name . "';";
      $query2 = "UPDATE `users` SET `first_name` = '" . $new_name . "' WHERE `first_name` = '" .  $current_name . "';";



      
      try 
      { 
   

          
          $result2 = mysqli_query($conn1, $query);
          $totalCount = mysqli_num_rows($result2);
    


          if ($_SERVER['REQUEST_METHOD'] === 'POST')
          {         
              
          if (!empty($_POST["current_name"]) && !empty($_POST["new_name"]) && !empty($_POST["password"]))


          {                
          if ($_POST["password"] == "password"){

              if ($totalCount == 0) {

              echo "<font color = '#FF0000'> Current username does not exist, please input a user that exists.";
          }

          else {
              $stmt = mysqli_query($conn1 , $query2);     
              echo "<font color = '#008000'> Username Modified.";
               

              }

              
          }

          else {
              echo "<font color = '#FF0000'> Incorrect Password.";

          }

      }


      else {
  
          echo "<font color = '#FF0000'> Empty Field.";
  
  
      }
      }


          foreach ($result as $row)  
          { 
              echo $row["first_name"];
          }
          // OUTPUT DATA OF EACH Row
          
          }

          catch(PDOException $e) { 
              echo "Error: " . $e->getMessage(); 
          } 
  

?>


</body>
</html>
//database_summary.php(User Analytics Page)

<!DOCTYPE html>
  <html lang="en">


  <head>
      <meta chaset="UTF-8">
      <meta http-equivx="X-UA-Compatible" content="IE=edge">
      <meta http-equiv="refresh" content="10">
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>User Analytics</title>
  </head>
  <h3> User Analytics </h3>

  <body>
      <a href="index.php" class=button>
      Home
      </a>
      <hr>
      <br>
      <style type="text/css">
          
          table {
          border-collaspe: collapse;
          width: 80%;
          color: #00008b;
          font-family: monospace;
          font-size: 25px;
          text-align: right; 
          }

          th{
              background-color: #00008b;
              color: white;


          }
          th {
            text-align: right;
          }
          tr:nth-child(even) {background-color: #f2f2f2};
      </style>

      
      <style>
                  .button {
                  background-color:  #00008b;
                  border: none;
                  color: white;
                  padding: 15px 40px;
                  text-align: center;
                  text-decoration: none;
                  display: inline-block;
                  font-size: 18px;
                  margin: 10px 10px;
                  cursor: pointer;
                  }
                  .button:hover {
                  background-color: #77C3EC;
                  color: white;
                  }
      </style>

      <table>
          <tr>
              <th>Name</th>
              <th>RFID</th>
              <th>WS1 Access Count</th>
              <th>WS2 Access Count</th>
              <th>WS1 Overall Time</th>
              <th>WS2 Overall Time</th>

      <?php 
      
      $servername = "localhost"; 
      $username = "default"; 
      $password = "password"; 
      $databasename = "final_project"; 
      
      // CREATE CONNECTION 
      $conn = mysqli_connect($servername,  
          $username, $password, $databasename); 
      
      // GET CONNECTION ERRORS 
      if (!$conn) { 
          die("Connection failed: " . mysqli_connect_error()); 
      } 
      
      // SQL QUERY 
      $query = "SELECT first_name, rfid, WS1_noof_access, WS2_noof_access,
         WS1_total_time, WS2_total_time FROM `users`;"; 
      
      try 
      { 
          $conn = new PDO( 
              "mysql:host=$servername;dbname=$databasename",  
              $username, $password); 
      
          $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
          $stmt = $conn->prepare($query); 
          // EXECUTING THE QUERY 
          $stmt->execute(); 
      
          $r = $stmt->setFetchMode(PDO::FETCH_ASSOC); 
          // FETCHING DATA FROM DATABASE 
          $result = $stmt->fetchAll(); 
          // OUTPUT DATA OF EACH Row
          
          foreach ($result as $row)  
          { 
              echo "<tr><td>". $row["first_name"] . "</td><td>" . $row["rfid"] . "</td><td>" 
              . $row["WS1_noof_access"] . "</td><td>" . $row["WS2_noof_access"] . "</td><td>" 
               . $row["WS1_total_time"] . "</td><td>" . $row["WS2_total_time"] . "</td><tr>";
              
          }
          
          echo "</table>";
      } catch(PDOException $e) { 
          echo "Error: " . $e->getMessage(); 
      } 
      
      $conn->close(); 
      
      ?>
   
  </body>

</html>

      
//remove_user.php (Remove User Page)

<!DOCTYPE html>
  <html lang="en">


  <head>
      <meta chaset="UTF-8">
      <meta http-equivx="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <title>Remove User</title>



      <style>
          .button {
          background-color:  #00008b;
          border: none;
          color: white;
          padding: 15px 40px;
          text-align: center;
          text-decoration: none;
          display: inline-block;
          font-size: 18px;
          margin: 10px 10px;
          cursor: pointer;
          }
          .button:hover {
                  background-color: #77C3EC;
                  color: white;
                  }
</style>

  </head>

  <h3>Remove User </h3>
  <hr>
  <br>

  <body>

      
  
  <form action="remove_user.php" method="post">
      Username: <input type="text", name="current_name">
      <br>
      <br>
      <br>

      Password:<input type="password", name="password">
      <br>
      <br>
      <br>
      
      <input type="submit" class=button font size = -10>

      <a href="index.php" class="button"> Home </a> 

</form>

<br> 
<?php 
      $run_flag = 0;
      $current_name = $_POST["current_name"];
      $password = $_POST["password"];
      $servername = "localhost"; 
      $username = "default"; 
      $password = "password"; 
      $databasename = "final_project"; 
      
      // CREATE CONNECTION 
      $conn1 = mysqli_connect($servername,  
          $username, $password, $databasename); 
      
      // GET CONNECTION ERRORS 
      if (!$conn1) { 
          die("Connection failed: " . mysqli_connect_error()); 
      } 

      // SQL QUERY 
      $query = "SELECT `rfid` FROM `users` WHERE `first_name` ='" .  $current_name . "';";
      $query2 = "DELETE FROM `users` WHERE `first_name` = '" .  $current_name . "';";

      

      // $query = "SELECT COUNT(*) FROM users WHERE first_name = 'Devin';"; 

      
      try 
      { 
          

          
          $result2 = mysqli_query($conn1, $query);
          $totalCount = mysqli_num_rows($result2);

          if ($_SERVER['REQUEST_METHOD'] === 'POST')
          {         
              
          if (!empty($_POST["current_name"]) && !empty($_POST["password"]))


          {                
          if ($_POST["password"] == "password"){

              if ($totalCount == 0) {

              echo "<font color = '#FF0000'> Username does not exist, please input a user that exists.";
          }

          else {
              $stmt = mysqli_query($conn1 , $query2);     
              echo "<font color = '#008000'> User Removed.";
               

              }

              
          }

          else {
              echo "<font color = '#FF0000'> Incorrect Password.";

          }

      }


      else {
  
          echo "<font color = '#FF0000'> Empty Field.";
  
  
      }
      }


          foreach ($result as $row)  
          { 
              echo $row["first_name"];
          }
          // OUTPUT DATA OF EACH Row
          
          }

          catch(PDOException $e) { 
              echo "Error: " . $e->getMessage(); 
          } 
  

?>


</body>
</html>